--[[
@module jx90614
@summary JX90614 红外测温传感器驱动
@version 2.0
@date    2024.01.01
@description 基于JX90614传感器数据手册实现的驱动，支持I2C通信，温度测量范围-70~380°C

@usage
-- 注意:因使用了sys.wait()所有api需要在协程中使用
local jx90614 = require "jx90614"
i2cid = 0
i2c_speed = i2c.FAST
sys.taskInit(function()
    i2c.setup(i2cid, i2c_speed)
    local ret = jx90614.init(i2cid)
    if ret then
        log.info("jx90614", "初始化成功")
        while 1 do
            local temp = jx90614.get_temperature()
            local ambient_temp = jx90614.get_ambient_temperature()
            if temp and ambient_temp then
                log.info("jx90614", "目标温度: " .. tostring(temp) .. "℃, 环境温度: " .. tostring(ambient_temp) .. "℃")
            end
            sys.wait(1000)
        end
    end
end)
]]

local jx90614 = {}
local sys = require "sys"
local i2cid
local pack = string.pack and string.unpack or require "pack"

-- JX90614传感器的I2C地址
local JX90614_ADDRESS = 0x7F -- 数据手册中的通配7-bit地址

-- 寄存器地址定义
local REG_DATA_READY    = 0x02 -- 数据就绪寄存器
local REG_TO            = 0x10 -- 目标温度寄存器(24位: 0x10, 0x11, 0x12)
local REG_TOCAL         = 0x19 -- 校准后热电堆数据寄存器(24位: 0x19, 0x1A, 0x1B)
local REG_TARAW         = 0x28 -- 环境温度原始数据寄存器(24位: 0x28, 0x29, 0x2A)
local REG_TACAL         = 0x31 -- 校准后环境温度寄存器(24位: 0x31, 0x32, 0x33)
local REG_CMD           = 0x30 -- 命令寄存器
local REG_CONFIG        = 0x80 -- 配置寄存器
local REG_CHIP_ID       = 0x92 -- 芯片ID和地址配置寄存器

-- 数据就绪寄存器位定义
local DATA_RDY_BIT      = 0x04 -- 温度转换完成位

-- 温度转换常量
local TEMP_CONVERSION_FACTOR = 16384.0 -- 2^14，用于温度转换

-- 内部函数：I2C写入寄存器
local function write_register(reg, data)
    if type(data) == "table" then
        -- 数组形式发送寄存器地址和数据
        local ret = i2c.send(i2cid, JX90614_ADDRESS, {reg, table.unpack(data)})
        return ret == true or ret == (#data + 1)
    else
        -- 发送寄存器地址和单个字节数据
        local ret = i2c.send(i2cid, JX90614_ADDRESS, {reg, data})
        return ret == true or ret == 2
    end
end

-- 内部函数：I2C读取寄存器
local function read_register(reg, len)
    -- 发送寄存器地址
    local ret = i2c.send(i2cid, JX90614_ADDRESS, reg)
    if ret ~= true and ret ~= 1 then
        log.info("jx90614", "发送寄存器地址失败: 0x" .. string.format("%02X", reg))
        return nil
    end
    
    -- 等待短暂时间确保地址设置完成
    sys.wait(2)
    
    -- 读取数据
    local data = i2c.recv(i2cid, JX90614_ADDRESS, len)
    if not data or #data == 0 then
        log.info("jx90614", "读取数据失败")
        return nil
    end
    
    -- 如果数据长度不匹配，记录警告但仍返回数据
    if #data ~= len then
        log.info("jx90614", "警告: 数据长度不匹配，期望: " .. len .. ", 实际: " .. #data)
    end
    
    return data
end

-- 内部函数：等待数据就绪
local function wait_for_data_ready(timeout_ms)
    timeout_ms = timeout_ms or 100
    local start_time = os.time() * 1000
    
    while (os.time() * 1000 - start_time) < timeout_ms do
        local ready_data = read_register(REG_DATA_READY, 1)
        if ready_data and #ready_data == 1 then
            local status = string.byte(ready_data)
            if (status & DATA_RDY_BIT) ~= 0 then
                return true -- 数据就绪
            end
        end
        sys.wait(5)
    end
    
    -- 数据就绪检查未通过，但这在某些工作模式下是正常的
    -- JX90614传感器可能持续更新数据寄存器而不触发就绪标志
    return false
end

-- 内部函数：解析24位补码温度数据
local function parse_temperature_data(data)
    if not data or #data < 3 then
        return nil
    end
    
    -- 解析三个字节数据
    local data1, data2, data3 = string.byte(data, 1, 3)
    
    -- 组合成24位整数
    local raw_value = data1 * 65536 + data2 * 256 + data3
    
    -- 处理补码
    if (data1 & 0x80) ~= 0 then
        -- 负数，减去2^24
        raw_value = raw_value - 16777216
    end
    
    -- 根据数据手册转换为温度值
    local temperature = raw_value / TEMP_CONVERSION_FACTOR
    
    return temperature
end

--[[
jx90614初始化
@api jx90614.init(i2c_id)
@number i2c_id 所在的i2c总线id
@return bool 成功返回true，失败返回false
@usage
local ret = jx90614.init(0)
if ret then
    log.info("jx90614", "初始化成功")
end
]]
function jx90614.init(i2c_id)
    i2cid = i2c_id
    
    -- 初始化延迟，确保传感器稳定
    sys.wait(100)
    
    log.info("jx90614", "开始初始化...")
    
    -- 尝试读取芯片ID，验证连接
    local chip_id_data = read_register(REG_CHIP_ID, 1)
    if chip_id_data then
        local chip_id = string.byte(chip_id_data)
        log.info("jx90614", "读取到芯片ID: 0x" .. string.format("%02X", chip_id))
    else
        log.info("jx90614", "注意：无法读取芯片ID，但继续尝试初始化")
    end
    
    -- 启动传感器，写入命令寄存器
    local cmd_success = write_register(REG_CMD, 0x08)
    if cmd_success then
        log.info("jx90614", "成功写入启动命令")
    else
        log.info("jx90614", "警告：写入启动命令失败，但继续")
    end
    
    -- 等待传感器初始化完成
    sys.wait(50)
    
    -- 检查数据就绪状态
    local ready_data = read_register(REG_DATA_READY, 1)
    if ready_data then  
        local status = string.byte(ready_data)
        log.info("jx90614", "数据就绪状态: 0x" .. string.format("%02X", status))
    end
    
    log.info("jx90614", "初始化完成")
    return true -- JX90614通常不需要复杂的初始化过程
end

--[[
获取目标温度
@api jx90614.get_temperature()
@return number 目标温度值(摄氏度)，失败返回nil
@usage
local temp = jx90614.get_temperature()
if temp then
    log.info("jx90614", "目标温度: " .. temp .. "℃")
end
]]
function jx90614.get_temperature()
    -- 高速率读取可直接读取数据寄存器，低速率应先检查就绪状态
    -- 这里为了兼容性先检查数据就绪
    local ready = wait_for_data_ready(50)
    
    -- 读取目标温度寄存器(24位数据)
    local temp_data = read_register(REG_TO, 3)
    if not temp_data then
        return nil
    end
    
    -- 解析温度数据
    local temperature = parse_temperature_data(temp_data)
    if temperature then
        -- 根据数据手册，JX90614温度范围为-70~380°C
        if temperature >= -70 and temperature <= 380 then
            log.info("jx90614", "目标温度: " .. tostring(temperature) .. "℃")
            return temperature
        else
            log.info("jx90614", "警告: 温度值超出有效范围: " .. tostring(temperature) .. "℃")
            return nil
        end
    end
    
    return nil
end

--[[
获取环境温度
@api jx90614.get_ambient_temperature()
@return number 环境温度值(摄氏度)，失败返回nil
@usage
local amb_temp = jx90614.get_ambient_temperature()
if amb_temp then
    log.info("jx90614", "环境温度: " .. amb_temp .. "℃")
end
]]
function jx90614.get_ambient_temperature()
    -- 读取环境温度原始数据
    local temp_data = read_register(REG_TARAW, 3)
    if not temp_data then
        return nil
    end
    
    -- 解析温度数据
    local temperature = parse_temperature_data(temp_data)
    if temperature then
        -- 环境温度通常不会超出工作温度范围-40~125°C
        if temperature >= -40 and temperature <= 125 then
            return temperature
        else
            log.info("jx90614", "警告: 环境温度值超出合理范围: " .. tostring(temperature) .. "℃")
            return nil
        end
    end
    
    return nil
end

--[[
获取校准后的热电堆数据
@api jx90614.get_tocal_data()
@return number 校准后的热电堆数据值
@usage
local tocal = jx90614.get_tocal_data()
]]
function jx90614.get_tocal_data()
    local tocal_data = read_register(REG_TOCAL, 3)
    if not tocal_data then
        return nil
    end
    
    return parse_temperature_data(tocal_data)
end

--[[
获取校准后的环境温度数据
@api jx90614.get_tacal_data()
@return number 校准后的环境温度数据值
@usage
local tacal = jx90614.get_tacal_data()
]]
function jx90614.get_tacal_data()
    local tacal_data = read_register(REG_TACAL, 3)
    if not tacal_data then
        return nil
    end
    
    return parse_temperature_data(tacal_data)
end

--[[
设置JX90614进入睡眠模式
@api jx90614.sleep()
@return bool 成功返回true
@usage
jx90614.sleep()
]]
function jx90614.sleep()
    -- 写入睡眠命令到命令寄存器
    local ret = write_register(REG_CMD, 0x00)
    log.info("jx90614", "进入睡眠模式")
    return ret
end

--[[
唤醒JX90614
@api jx90614.wakeup()
@return bool 成功返回true
@usage
jx90614.wakeup()
]]
function jx90614.wakeup()
    -- 写入唤醒命令到命令寄存器
    local ret = write_register(REG_CMD, 0x08)
    sys.wait(100) -- 唤醒需要一定时间
    log.info("jx90614", "从睡眠模式唤醒")
    return ret
end

return jx90614